<template>
  <div
    class="progress-warp"
    :class="{
      'progress-moving':moving,
      'progress-horizontal':direction==='horizontal',
      'progress-vertical':direction==='vertical',
      'progress-disabled':disabled
    }"
  >
    <div class="progress-preload" />
    <div class="progress-container">
      <span class="progress-control" />
    </div>
    <div v-if="direction==='horizontal'&&isTips" class="progress-tips">
      <slot name="tips" :tips="tips">
        {{ tips.toFixed(2) }}
      </slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'UiProgress',
  props: {
    direction: {
      type: String,
      default: 'horizontal',
      validator(value) {
        const index = ['horizontal', 'vertical'].indexOf(value)
        return index !== -1
      }
    },
    progress: {
      type: Number,
      default: 0
    },
    preload: {
      type: Number,
      default: 0
    },
    disabled: {
      type: Boolean,
      default: false
    },
    sync: {
      type: Boolean,
      default: false
    },
    isTips: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      moving: false,
      isUp: false,
      tips: 0
    }
  },
  computed: {
    key() {
      return this.direction === 'horizontal' ? 'width' : 'height'
    }
  },
  watch: {
    preload(val, oldVal) {
      if (val !== oldVal) {
        this.setPayload(val, this.direction)
      }
    },
    progress(val, oldVal) {
      if (val !== oldVal) {
        if (!this.sync && this.moving) {
          return
        }
        this.setProgress(val, this.direction, true)
      }
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    // 获取鼠标相对元素的位置
    getPos(e, ele) {
      const pos = this.getElePos(ele)
      const dx = e.clientX - pos.x
      const dy = e.clientY - pos.y
      return { dx, dy }
    },
    getElePos(ele) {
      const pos = {
        x: ele.offsetLeft,
        y: ele.offsetTop
      }
      if (ele.offsetParent != null) {
        pos.x += this.getElePos(ele.offsetParent).x
        pos.y += this.getElePos(ele.offsetParent).y
      }
      return pos
    },
    setProgress(progress, sync) {
      const key = this.key
      if (progress < 0) {
        progress = 0
      }
      if (progress > 1) {
        progress = 1
      }
      this.$container.style[key] = (progress * 100) + '%'
      if (!this.sync && this.moving) {
        return
      }
      if (sync) {
        return
      }
      this.$emit('progress-change', progress)
    },
    calculateProgress(setWidth) {
      const key = this.direction === 'horizontal' ? 'offsetWidth' : 'offsetHeight'
      const totalWidth = this.$warp[key]
      if (setWidth < 0) {
        return 0
      } else if (setWidth >= 0 && setWidth <= totalWidth) {
        return setWidth / totalWidth
      } else {
        return 1
      }
    },
    init() {
      this.$warp = this.$el
      this.$preload = this.$el.querySelector('.progress-preload')
      this.$container = this.$el.querySelector('.progress-container')
      this.$control = this.$el.querySelector('.progress-control')
      this.$tips = this.$el.querySelector('.progress-tips')
      this.$el.addEventListener('click', this.mouseClick)
      this.$control.addEventListener('mousedown', this.mouseDown)
      this.$control.addEventListener('touchstart', this.touchStart)
      if (this.direction === 'horizontal' && this.isTips) {
        this.$warp.addEventListener('mousemove', this.tipsMove)
      }
      this.setPayload(this.preload)
      this.setProgress(this.progress)
    },
    mouseDown(e) {
      this.start(e, 'mouse')
    },
    mouseMove(e) {
      this.move(e)
    },
    mouseUp(e) {
      this.end(e, 'mouse')
    },
    mouseClick(e) {
      if (this.disabled) {
        return
      }
      if (this.isUp) {
        this.isUp = false
        return
      }
      const pos = this.getPos(e, this.$warp)
      let setWidth = 0
      if (this.direction === 'horizontal') {
        setWidth = pos.dx
      } else {
        setWidth = this.$warp.offsetHeight - pos.dy
      }
      const progress = this.calculateProgress(setWidth)
      this.setProgress(progress)
    },
    touchStart(event) {
      const e = event.touches[0]
      this.start(e, 'touch')
    },
    touchMove(event) {
      const e = event.touches[0]
      this.move(e)
    },
    touchEnd(event) {
      const e = event.changedTouches[0]
      this.end(e, 'touch')
    },
    setPayload(progress) {
      const key = this.key
      if (progress < 0) {
        progress = 0
      }
      if (progress > 1) {
        progress = 1
      }
      this.$preload.style[key] = (progress * 100) + '%'
    },
    end(e, type) {
      const _this = this
      const pos = {
        dx: e.clientX - this.state.x,
        dy: e.clientY - this.state.y
      }
      let barWidth = this.state.initWidth + pos.dx
      if (this.direction === 'vertical') {
        barWidth = this.state.initWidth - pos.dy
      }
      const progress = this.calculateProgress(barWidth)
      this.moving = false
      this.setProgress(progress)
      if (type === 'touch') {
        document.removeEventListener('touchmove', this.touchMove)
        document.removeEventListener('touchend', this.touchEnd)
      }
      if (type === 'mouse') {
        document.removeEventListener('mousemove', this.mouseMove)
        document.removeEventListener('mouseup', this.mouseUp)
      }
      if (this.isUp) {
        const inter = setTimeout(() => {
          _this.isUp = false
          clearTimeout(inter)
        }, 5)
      }
    },
    move(e) {
      if (!this.moving) {
        return
      }
      const pos = {
        dx: e.clientX - this.state.x,
        dy: e.clientY - this.state.y
      }
      let barWidth = this.state.initWidth + pos.dx
      if (this.direction === 'vertical') {
        barWidth = this.state.initWidth - pos.dy
      }
      const progress = this.calculateProgress(barWidth)
      this.setProgress(progress)
      this.isUp = true
    },
    start(e, type) {
      if (this.disabled) {
        return
      }
      const key = this.direction === 'horizontal' ? 'offsetWidth' : 'offsetHeight'
      this.state = {
        x: e.clientX,
        y: e.clientY,
        initWidth: this.$container[key]
      }
      this.moving = true
      if (type === 'touch') {
        document.addEventListener('touchmove', this.touchMove)
        document.addEventListener('touchend', this.touchEnd)
      }
      if (type === 'mouse') {
        document.addEventListener('mousemove', this.mouseMove)
        document.addEventListener('mouseup', this.mouseUp)
      }
    },
    tipsMove(e) {
      const pos = this.getPos(e, this.$warp)
      const progress = this.calculateProgress(pos.dx)
      this.tips = progress
      const totalWidth = this.$warp.offsetWidth
      const tipsWidth = this.$tips.offsetWidth
      let setW = totalWidth * progress - (tipsWidth / 2)
      const max = (totalWidth - tipsWidth) / totalWidth
      setW = setW / totalWidth
      if (setW < 0) {
        setW = 0
      }
      if (setW > max) {
        setW = max
      }
      this.$tips.style.left = (setW * 100) + '%'
    }
  }
}
</script>

<style scoped lang="less">
  @control-color:#ffffff;
  @preload-color:#c8c8c8;
  @progress-color:#e63e53;
  @bg-color:#6a737d;
  .progress-warp{
    position: relative;
    background-color: @bg-color;
    cursor: pointer;
    border-radius: 2px;
    user-select: none;
    -webkit-tap-highlight-color:transparent;
  }
  .progress-control{
    width:10px;
    height: 10px;
    border-radius: 5px;
    position: absolute;
    background-color: @control-color;
    opacity: 0;
    transform: scale(.5);
    box-shadow: 0 0 7px 1px @progress-color;
    transition: all .2s;
  }
  .progress-warp:hover .progress-control,
  .progress-moving .progress-control{
    opacity: 1;
    transform: scale(1);
  }
  .progress-container{
    position: relative;
    background-color: @progress-color;
    border-radius: 2px;
  }
  .progress-preload{
    position: absolute;
    background-color: @preload-color;
    border-radius: 2px;
  }
  .progress-horizontal{
    width: 100%;
    height: 4px;
  }
  .progress-horizontal .progress-container{
    height: 100%;
    width: 0;
  }
  .progress-horizontal .progress-preload{
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 100%;
  }
  .progress-horizontal .progress-control{
    left: 100%;
    margin-left: -5px;
    top: 50%;
    margin-top: -5px;
  }
  .progress-vertical{
    width: 4px;
    height:100%;
  }
  .progress-vertical .progress-container{
    height: 0;
    width: 100%;
    position: absolute;
    left: 0;
    bottom: 0;
  }
  .progress-vertical .progress-preload{
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 0;
  }
  .progress-vertical .progress-control{
    bottom: 100%;
    margin-left: -5px;
    left:50%;
    margin-bottom: -5px;
  }
  .progress-tips{
    position: absolute;
    font-size: 12px;
    color: #ffffff;
    line-height: 18px;
    background-color: rgba(48, 48, 48, 0.6);
    border-radius: 2px;
    text-align: center;
    min-width: 20px;
    padding: 0 5px;
  }
  .progress-horizontal .progress-tips{
    left: 0;
    bottom: 100%;
    margin-bottom: 5px;
    opacity: 0;
    visibility: hidden;
  }
  .progress-horizontal:hover .progress-tips{
    opacity: 1;
    visibility: visible;
  }
</style>
